//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi.font;

import java.io.IOException;
import ibook.v10.idvi.io.DVIInputStream;

/**
 * Internal representation of a single character from a PK font file.
 * @see PKFont
 * @version 1.02 22 Jan 1996
 * @author Garth A. Dickie
 */
public final class PKCharacter extends DVICharacter {
    private byte[ ] encodedBits;
    private int     dynf;
    private boolean paint;

    private void decodeUncompressedCharacter( ) {
        PKBitStream source = new PKBitStream( encodedBits );

        for( int i = 0; i < bits.length; ++ i )
            bits[ i ] = ( byte ) source.bit( );
    }

    /**
     * read compressed data into the array 'bits'.
     * rows are concatenated with no padding, and then encoded as
     * a sequence of run-lengths.  To obtain the run-lengths, we
     * read 4 bits at a time, and decode: <p>
     * 
     * 15            repeat the current row once when it is full. <p>
     * 14            use next run-length as the number of times to
     *               repeat this row when it is full, instead of using it as a run-length. <p>
     * 13 ... dynf+1 compute run-length from this value, next 4 bits, and dynf. <p>
     *  1 ... dynf   run-length is this value.<p>
     *  0            number of 4-bit zeros is how many more 4-bit numbers are
     *               to be concatenated, then crunched with dynf to compute run-length.<p>
     */
    private void decodeCompressedCharacter( ) {

        PKNybbleStream source = new PKNybbleStream( encodedBits );

        int bitIndex = 0;
        int rowRepeat = 0;
        boolean setRowRepeat = false;

        while( bitIndex < bits.length ) {
            int command = source.nybble( );

            if( command == 0xF ) {
                rowRepeat = 1;
            } else if( command == 0xE ) {
                setRowRepeat = true;
            } else {
                int value;

                if( command > dynf ) {
                    value = 16 * command + source.nybble( ) - 15 * ( dynf + 1 );
                } else if( command > 0 ) {
                    value = command;
                } else {
                    int nybbleCount = 1;
                    int temp;

                    while(( temp = source.nybble( )) == 0 )
                        ++ nybbleCount;
                    
                    while(( nybbleCount -- ) != 0 )
                        temp = ( temp << 4 ) | source.nybble( );
                    
                    value = temp + 193 - 15 * dynf;
                }

                if( setRowRepeat ) {
                    rowRepeat = value;
                    setRowRepeat = false;
                } else {

                    //  this is where we actually set bits in the array

                    if( rowRepeat != 0 ) {
                        int widthLeft = width - bitIndex % width;
                        if( widthLeft <= value ) {
                            value -= widthLeft;

                            if( paint )
                                for( int target = bitIndex + widthLeft; bitIndex < target; ++ bitIndex )
                                    bits[ bitIndex ] = 1;
                            else bitIndex += widthLeft;

                            for( int target = bitIndex + rowRepeat * width; bitIndex < target; ++ bitIndex )
                                bits[ bitIndex ] = bits[ bitIndex - width ];

                            rowRepeat = 0;
                        }
                    }

                    if( paint )
                        for( int target = bitIndex + value; bitIndex < target; ++ bitIndex )
                            bits[ bitIndex ] = 1;
                    else bitIndex += value;

                    paint = ! paint;
                }
            }
        }
    }

    /**
     * Construct a PKCharacter from a font record in a PK file.
     * @param source DVIInputStream to read character data from.
     * @param dynf high four bits of flag byte, range 0 to 14.
     * @param paint true if top left pixel is set.
     * @param integerSize number of bytes each integer takes up in the preamble.
     */
    PKCharacter( DVIInputStream source, int dynf, boolean paint, int integerSize, int byteCount )
            throws IOException, PKFormatException {

        this.dynf = dynf;
        this.paint = paint;

        tfmWidth = source.signed( integerSize == 4 ? 4 : 3 );
        source.skip( integerSize == 4 ? 8 : integerSize );
        width = source.unsigned( integerSize );
        height = source.unsigned( integerSize );
        xOffset = source.signed( integerSize );
        yOffset = source.signed( integerSize );

        int used = 4 * integerSize + ( integerSize == 4 ? 12 : 3 + integerSize );

        encodedBits = new byte[ byteCount - used ];
        source.read( encodedBits );
    }

    // not synchronized, since all callers are already synchronized on this
    // object.  (called by base class DVICharacter).

    public void decodeBits( ) {
        if( bits == null ) {
            bits = new byte[ width * height ];

            if( dynf == 14 )
                decodeUncompressedCharacter( );
            else
                decodeCompressedCharacter( );
            
            encodedBits = null;
        }
    }
}

final class PKNybbleStream {
    byte[ ] buffer;
    int     index = 0;

    int     nybbleCache;
    boolean nybbleCached = false;

    PKNybbleStream( byte[ ] buffer ) {
        this.buffer = buffer;
    }

    int nybble( ) {
        if( nybbleCached ) {
            nybbleCached = false;
            return nybbleCache & 0xF;
        } else {
            nybbleCached = true;
            nybbleCache = buffer[ index ++ ];
            return ( nybbleCache >> 4 ) & 0xF;
        }
    }
}

final class PKBitStream {
    byte[ ] buffer;
    int     index = 0;

    int     bitCache;
    int     shift = 0;

    PKBitStream( byte[ ] buffer ) {
        this.buffer = buffer;
    }

    int bit( ) {
        if( shift == 0 ) {
            bitCache = buffer[ index ++ ];
            shift = 8;
        }
        return ( bitCache >> -- shift ) & 1;
    }
}
